package se392.ateam2006.meetingmgmt;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Vector;
import javax.ejb.CreateException;
import javax.ejb.EJB;
import javax.ejb.FinderException;
import javax.ejb.Stateless;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.Query;
import java.util.GregorianCalendar;

import se392.ateam2006.exceptions.InvalidValueException;
import se392.ateam2006.meetingmgmt.meeting.*;
import se392.ateam2006.resourcemgmt.ResourceMgmtBean;
import se392.ateam2006.resourcemgmt.ResourceMgmtLocal;
import se392.ateam2006.resourcemgmt.ResourceMgmtRemote;
import se392.ateam2006.resourcemgmt.participant.ParticipationEntity;
import se392.ateam2006.resourcemgmt.participant.exclusionset.ExclusionRangeEntity;
import se392.ateam2006.resourcemgmt.room.RoomEntity;
import se392.ateam2006.user.UserEntity;
import se392.ateam2006.user.UserMgmtBean;
import se392.ateam2006.user.UserMgmtLocal;
import se392.ateam2006.user.UserMgmtRemote;
import se392.ateam2006.exceptions.MethodNotImplementedException;

/**
 * A stateless EJB to handle all operations that deal with meetings - create/delete metting etc
 * @author Ateam (Matthew Bennett, Claire Melton, Shingai Manyiwa, John Adderley)
 * @version 25/03/07
 */
@Stateless
public class MeetingMgmtBean implements MeetingMgmtRemote, MeetingMgmtLocal {
    
    @PersistenceContext
    private EntityManager em;
    
    public static int defaultCutOffPeriod = 1;
    
    @EJB
    private ResourceMgmtLocal resources;
    @EJB
    private UserMgmtLocal users;
    
    /**
     * Default constructor required as this is an EJB
     */
    public MeetingMgmtBean() { }
    
    /**
     * Create a new MeetingEntity and persist it
     * @param initiator - the UserEntity who initiated the meeting
     * @param id - a String id for the meeting
     * @param title - a String title for the meeting
     * @param description - a String description of the meeting
     * @param startDateRange - a GregorianCalendar defining the start date/time of the range that
     *      the meeting may exist in
     * @param endDateRange - a GregorianCalendar defining the end date/time of the range that
     *      the meeting may exist in
     * @param duration - a Duration defining the length of the meeting
     * @param requestedRoomSize - int, the number of people we need to accomodate
     * @param equipment - String, any equipment needed for the meeting
     * @param room - the RoomEntity where this meeting will take place
     * @throws CreateException
     */
    public void createMeeting(UserEntity initiator, String id, String title, String description,
            GregorianCalendar startDateRange, GregorianCalendar endDateRange,
            Duration duration, int requestedRoomSize, String equipment, RoomEntity room) throws CreateException {
        
        em.flush();
        
        // Check if the meeting already exists
        MeetingEntity ourbean = (MeetingEntity) em.find(MeetingEntity.class, id);
        if(ourbean == null) {
            
            // If it doesn't exist, create it
            em.merge(duration);
            em.merge(initiator);
            MeetingEntity meeting = null;
            if(room != null) {
                em.merge(room);
            }
            if(null == startDateRange){
                throw new CreateException("The start date range has not been specified.");
            }else if(null == endDateRange){
                throw new CreateException("The end date range has not been specified.");
            }else if(null == duration ){
                throw new CreateException("Please specify a valid duration");
            }else if(null == initiator || initiator.equals("")){ //TODO: make this make sense
                throw new CreateException("Meeting User not valid");
            }else if (null == id || id.equals("")){
                throw new CreateException("Meeting PK (ID) not valid");
            }else if (startDateRange.equals(endDateRange)){
                throw new CreateException("Start Date is the Same as the End Date");
            }else if (startDateRange.getTimeInMillis() > endDateRange.getTimeInMillis()){
                throw new CreateException("Start Date is the after the End Date");
            }else if((endDateRange.getTimeInMillis() - startDateRange.getTimeInMillis()) < duration.getTimeInMillis() ){
                throw new CreateException("The duration is longer than the meeting range");
            }else if((endDateRange.getTimeInMillis() - startDateRange.getTimeInMillis()) < (1 * 24 * 60 * 60 * 1000)){ //TODO: first number should be cutOffPeriod!
                throw new CreateException("The meeting has a range which is less than the cut off period. Cut-Off Period: 1");//TODO: make this relative to above
            }else if(room != null && room.getCapacity() < requestedRoomSize)
                throw new CreateException("Room is too small for meeting");
            
            meeting = new MeetingEntity(initiator, id, title, description, startDateRange, endDateRange,
                    duration, requestedRoomSize, equipment, room);
            
            em.persist(meeting);
            System.out.println("MeetingMgmt.createMeeting() has successfully added meeting: " + id + " (initiator: " + initiator + ")");
        } else {
            System.out.println("MEETING FOUND WAS "+ourbean);
            throw new CreateException("The meeting with id: \"" + id  +"\" already exists.");
        }
    }
    
    /**
     * Create a new Duration and persist it
     * @param days - int, number of days in the duration
     * @param hours - int, the number of hours in the duration
     * @param mins - int, the number of minutes in the duration
     * @throws CreateException
     */
    public Duration createDuration(int days, int hours, int mins) throws CreateException{
        Duration duration = null;
        if(mins <= 10 && hours < 1 && days < 1){
            throw new CreateException("Please specify a valid duration");
        }
        try {
            duration = new Duration(days, hours, mins);
        } catch (InvalidValueException ex) {
            ex.printStackTrace();
        }
        em.persist(duration);
        return duration;
    }
    
    /**
     * Find a duration based on its ID
     * @param id - long, the ID of the duration to find
     * @return Duration - the found duration (or null if not found)
     */
    public Duration findDurationByID(long id) {
        return em.find(Duration.class,id);
    }
    
    /**
     * Find a meeting based on its ID
     * @param id - String, the ID of the meeting to find
     * @return MeetingEntity - the foud MeetingEntity (or null if not found)
     */
    public MeetingEntity findMeetingById(String id) {
        return em.find(MeetingEntity.class, id);
    }
    
    /**
     * Delete a meeting
     * @param me - the MeetingEntity to delete
     */
    public void deleteMeeting(MeetingEntity me) {
        deleteMeetingById(me.getId());
    }
    
    /**
     * Delete a meeting based on its ID
     * @param id - String, the ID of the meeting to delete
     */
    public void deleteMeetingById(String id) {
        em.flush();
        List participations = null;
        List exclusions = null;
        Query q = null;
        
        try {
            //DEPENDANCIES:
            //  Todo: If you can get cascading deletes to work here, do!
            //  Todo: Also, there should be some find x by y methods for this!
            // -- PARTICIPANTS
            
            q = em.createQuery("Select p from ParticipationEntity p " +
                    "where p.meeting.id = '" + id + "'");
            participations = q.getResultList();
            for(Object item : participations) {
                ParticipationEntity p = (ParticipationEntity) item;
                resources.removeParticipation(p);
            }
            
            // -- EXCLUSIONS
            q = em.createQuery("Select p from ExclusionRangeEntity p " +
                    "where p.meetingEnt.id = '" + id + "'");
            exclusions = q.getResultList();
            for(Object item : exclusions) {
                ExclusionRangeEntity p = (ExclusionRangeEntity) item;
                resources.removePreAndExSet(p);
            }
            
            
            //MEETING
            MeetingEntity me = em.find(MeetingEntity.class, id);
            if(me != null) {
                em.remove(me);
            }
            q = em.createQuery("DELETE FROM MeetingEntity p WHERE p.id = '" + id + "'");
            q.executeUpdate();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * Set an existing meeting as booked
     * @param meetingId - String, the ID of the meeting to update
     * @param booked - boolean, is the meeting booked?
     */
    public void setMeetingAsBooked(String meetingId, boolean booked) {
        MeetingEntity me = em.find(MeetingEntity.class, meetingId);
        me.setBooked(booked);
        em.merge(me);
    }
    
    /**
     * Get all of the meetings who share the specified initiator
     * @param user - the UserEntity to look for
     * @return Collection - a collection of all meetings that were initiated by 'user'
     */
    public Collection findMeetingsByInitiator(UserEntity user) {
        em.flush();
        Query q = em.createQuery("SELECT p FROM MeetingEntity p WHERE p.initiator.email = '" + user.getEmail() + "'");
        //Query q = em.createNativeQuery("SELECT * FROM ADMIN.MEETINGENTITY WHERE INITIATOR_EMAIL = '" + user.getEmail() + "'");
        List list = q.getResultList();
        
        return list;
    }
    
    /**
     * Get all of the meetings in the database
     * @return Collection - all of the meetings in the database
     */
    public Collection getAllMeetings() {
        em.flush();
        
        Query q = em.createQuery("SELECT p FROM MeetingEntity p");
        return q.getResultList();
    }
    
    /**
     * Find a ParticipationEntity based on its ID (which is the email address of the user)
     * @param id - String, the ID of the participation entity to find
     * @return ParticipationEntity - the found ParticipationEntity
     */
    private ParticipationEntity findParticipationEntityByEmail(String id) {
        
        Query q = em.createQuery("SELECT p FROM ParticipationEntity WHERE id.email = '" + id +"'");
        return (ParticipationEntity)(q.getSingleResult());
    }
    
    /**
     * Get all of the booked meetings in the database
     * @return Collection - all of the booked meetings
     */
    public Collection getBookedMeetings() {
        
        Query q = em.createQuery("SELECT p FROM MeetingEntity p where p.booked = true");
        return q.getResultList();
    }
    
    /**
     * Get the number of meetings in the database
     * @return int - the number of meetings
     */
    public int getMeetingCount() {
        
        Query q = em.createQuery("SELECT p FROM MeetingEntity p");
        return q.getResultList().size();
    }
    
    /**
     * Remove all of the meetings from the database
     */
    public void removeAllMeetings() {
        
        Collection meets = getAllMeetings();
        for(Object me : meets) {
            this.deleteMeeting((MeetingEntity) me);
        }
        
    }
    
    /**
     * Find all meetings for a specified user
     * @param userId - String, the ID of the user who's meetings we want to find
     * @return Collection - all of the meetings that the user will be attending
     */
    public Collection<MeetingEntity> findMeetings(String userId) {
        throw new MethodNotImplementedException("MeetingMgmtBean.findMeetings()");
    }
    
    public void removeAllEquipment() {
        throw new MethodNotImplementedException("MeetingMgmtBean.removeAllEquipment()");
    }
    
    /**
     * Retrieves value that MeetingEntity should use as default cutoff period 
     * @return the current default cutoff period
     */
    public int getDefaultCutOffPeriod() {
        return MeetingMgmtBean.defaultCutOffPeriod;
    }
    
    
    /**
     * Sets the value that Meetings should default to as response cutoff
     * @param aDefaultCutOffPeriod - int, the new cutoff period
     */
    public void setDefaultCutOffPeriod(int aDefaultCutOffPeriod) {
        MeetingMgmtBean.defaultCutOffPeriod = aDefaultCutOffPeriod;
    }
    
}
